﻿//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Mapping Application Block
//===============================================================================
// All credit must go to Ruben Hakobian (c) 2009 for this class
// original article: http://web1.codeproject.com/KB/WPF/MaskableTextBox.aspx
//===============================================================================

using System;
using System.Windows;
using System.Windows.Controls;
using System.Globalization;

namespace EntLibContrib.Mapping.QuickStart
{
	/// <summary>
	/// WPF Maskable TextBox class. Just specify the TextBoxMaskBehavior.Mask attached property to a TextBox.
	/// It protect your TextBox from unwanted non numeric symbols and make it easy to modify your numbers.
	/// </summary>
	/// <remarks>
	/// Class Information:
	/// <list type="bullet">
	/// 		<item name="authors">Authors: Ruben Hakopian</item>
	/// 		<item name="date">February 2009</item>
	/// 		<item name="originalURL">http://www.rubenhak.com/?p=8</item>
	/// 	</list>
	/// </remarks>
	public class TextBoxMaskBehavior
	{
		#region MinimumValue Property

		/// <summary>
		/// Gets the minimum value.
		/// </summary>
		/// <param name="dependencyObject">The dependency object.</param>
		/// <returns></returns>
		public static double GetMinimumValue(DependencyObject dependencyObject)
		{
			return (double)dependencyObject.GetValue(MinimumValueProperty);
		}

		/// <summary>
		/// Sets the minimum value.
		/// </summary>
		/// <param name="dependencyObject">The dependency object.</param>
		/// <param name="value">The value.</param>
		public static void SetMinimumValue(DependencyObject dependencyObject, double value)
		{
			dependencyObject.SetValue(MinimumValueProperty, value);
		}

		/// <summary>
		/// Minimum Value dependency property
		/// </summary>
		public static readonly DependencyProperty MinimumValueProperty =
				DependencyProperty.RegisterAttached(
						"MinimumValue",
						typeof(double),
						typeof(TextBoxMaskBehavior),
						new FrameworkPropertyMetadata(double.NaN, MinimumValueChangedCallback)
						);

		private static void MinimumValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			TextBox textBox = (d as TextBox);
			if (textBox != null)
				ValidateTextBox(textBox);
		}
		#endregion

		#region MaximumValue Property

		/// <summary>
		/// Gets the maximum value.
		/// </summary>
		/// <param name="dependencyObject">The dependency object.</param>
		/// <returns></returns>
		public static double GetMaximumValue(DependencyObject dependencyObject)
		{
			return (double)dependencyObject.GetValue(MaximumValueProperty);
		}

		/// <summary>
		/// Sets the maximum value.
		/// </summary>
		/// <param name="dependencyObject">The dependency object.</param>
		/// <param name="value">The value.</param>
		public static void SetMaximumValue(DependencyObject dependencyObject, double value)
		{
			dependencyObject.SetValue(MaximumValueProperty, value);
		}

		/// <summary>
		/// Maximum Value dependency property
		/// </summary>
		public static readonly DependencyProperty MaximumValueProperty =
				DependencyProperty.RegisterAttached(
						"MaximumValue",
						typeof(double),
						typeof(TextBoxMaskBehavior),
						new FrameworkPropertyMetadata(double.NaN, MaximumValueChangedCallback)
						);

		private static void MaximumValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			TextBox textBox = (d as TextBox);
			if (textBox != null)
				ValidateTextBox(textBox);
		}
		#endregion

		#region Mask Property

		public static MaskType GetMask(DependencyObject dependencyObject)
		{
			return (MaskType)dependencyObject.GetValue(MaskProperty);
		}

		public static void SetMask(DependencyObject dependencyObject, MaskType value)
		{
			dependencyObject.SetValue(MaskProperty, value);
		}

		public static readonly DependencyProperty MaskProperty =
				DependencyProperty.RegisterAttached(
						"Mask",
						typeof(MaskType),
						typeof(TextBoxMaskBehavior),
						new FrameworkPropertyMetadata(MaskChangedCallback)
						);

		private static void MaskChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			TextBox oldTextBox = (e.OldValue as TextBox);
			if (oldTextBox != null)
			{
				oldTextBox.PreviewTextInput -= TextBox_PreviewTextInput;
				DataObject.RemovePastingHandler(oldTextBox, (DataObjectPastingEventHandler)TextBoxPastingEventHandler);
			}

			TextBox textBox = (d as TextBox);
			if (textBox == null)
				return;

			if ((MaskType)e.NewValue != MaskType.Any)
			{
				textBox.PreviewTextInput += TextBox_PreviewTextInput;
				DataObject.AddPastingHandler(textBox, (DataObjectPastingEventHandler)TextBoxPastingEventHandler);
			}

			ValidateTextBox(textBox);
		}

		#endregion

		#region Private Static Methods

		private static void ValidateTextBox(TextBox textBox)
		{
			if (GetMask(textBox) != MaskType.Any)
			{
				textBox.Text = ValidateValue(GetMask(textBox), textBox.Text, GetMinimumValue(textBox), GetMaximumValue(textBox));
			}
		}

		private static void TextBoxPastingEventHandler(object sender, DataObjectPastingEventArgs e)
		{
			TextBox textBox = (sender as TextBox);
			string clipboard = e.DataObject.GetData(typeof(string)) as string;
			clipboard = ValidateValue(GetMask(textBox), clipboard, GetMinimumValue(textBox), GetMaximumValue(textBox));
			if (!string.IsNullOrEmpty(clipboard))
			{
				textBox.Text = clipboard;
			}
			e.CancelCommand();
			e.Handled = true;
		}

		private static void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
		{
			TextBox textBox = (sender as TextBox);
			bool isValid = IsSymbolValid(GetMask(textBox), e.Text);
			e.Handled = !isValid;
			if (isValid)
			{
				int caret = textBox.CaretIndex;
				string text = textBox.Text;
				bool textInserted = false;
				int selectionLength = 0;

				if (textBox.SelectionLength > 0)
				{
					text = text.Substring(0, textBox.SelectionStart) +
									text.Substring(textBox.SelectionStart + textBox.SelectionLength);
					caret = textBox.SelectionStart;
				}

				if (e.Text == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
				{
					while (true)
					{
						int ind = text.IndexOf(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator);
						if (ind == -1)
							break;

						text = text.Substring(0, ind) + text.Substring(ind + 1);
						if (caret > ind)
							caret--;
					}

					if (caret == 0)
					{
						text = "0" + text;
						caret++;
					}
					else
					{
						if (caret == 1 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign)
						{
							text = NumberFormatInfo.CurrentInfo.NegativeSign + "0" + text.Substring(1);
							caret++;
						}
					}

					if (caret == text.Length)
					{
						selectionLength = 1;
						textInserted = true;
						text = text + NumberFormatInfo.CurrentInfo.NumberDecimalSeparator + "0";
						caret++;
					}
				}
				else if (e.Text == NumberFormatInfo.CurrentInfo.NegativeSign)
				{
					textInserted = true;
					if (textBox.Text.Contains(NumberFormatInfo.CurrentInfo.NegativeSign))
					{
						text = text.Replace(NumberFormatInfo.CurrentInfo.NegativeSign, string.Empty);
						if (caret != 0)
							caret--;
					}
					else
					{
						text = NumberFormatInfo.CurrentInfo.NegativeSign + textBox.Text;
						caret++;
					}
				}

				if (!textInserted)
				{
					text = text.Substring(0, caret) + e.Text +
							((caret < textBox.Text.Length) ? text.Substring(caret) : string.Empty);

					caret++;
				}

				try
				{
					double val = Convert.ToDouble(text);
					double newVal = ValidateLimits(GetMinimumValue(textBox), GetMaximumValue(textBox), val);
					if (val != newVal)
					{
						text = newVal.ToString();
					}
					else if (val == 0)
					{
						if (!text.Contains(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator))
							text = "0";
					}
				}
				catch
				{
					text = "0";
				}

				while (text.Length > 1 && text[0] == '0' && string.Empty + text[1] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
				{
					text = text.Substring(1);
					if (caret > 0)
						caret--;
				}

				while (text.Length > 2 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign && text[1] == '0' && string.Empty + text[2] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
				{
					text = NumberFormatInfo.CurrentInfo.NegativeSign + text.Substring(2);
					if (caret > 1)
						caret--;
				}

				if (caret > text.Length)
					caret = text.Length;

				textBox.Text = text;
				textBox.CaretIndex = caret;
				textBox.SelectionStart = caret;
				textBox.SelectionLength = selectionLength;
				e.Handled = true;
			}
		}

		private static string ValidateValue(MaskType mask, string value, double min, double max)
		{
			if (string.IsNullOrEmpty(value))
				return string.Empty;

			value = value.Trim();
			switch (mask)
			{
				case MaskType.Integer:
					try
					{
						Convert.ToInt64(value);
						return value;
					}
					catch
					{
					}
					return string.Empty;

				case MaskType.Decimal:
					try
					{
						Convert.ToDouble(value);

						return value;
					}
					catch
					{
					}
					return string.Empty;
			}

			return value;
		}

		private static double ValidateLimits(double min, double max, double value)
		{
			if (!min.Equals(double.NaN))
			{
				if (value < min)
					return min;
			}

			if (!max.Equals(double.NaN))
			{
				if (value > max)
					return max;
			}

			return value;
		}

		private static bool IsSymbolValid(MaskType mask, string str)
		{
			switch (mask)
			{
				case MaskType.Any:
					return true;

				case MaskType.Integer:
					if (str == NumberFormatInfo.CurrentInfo.NegativeSign)
						return true;
					break;

				case MaskType.Decimal:
					if (str == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator ||
							str == NumberFormatInfo.CurrentInfo.NegativeSign)
						return true;
					break;
			}

			if (mask.Equals(MaskType.Integer) || mask.Equals(MaskType.Decimal))
			{
				foreach (char ch in str)
				{
					if (!Char.IsDigit(ch))
						return false;
				}

				return true;
			}

			return false;
		}

		#endregion
	}

	#region Enumerators
	/// <summary>
	/// TextBox Mask Type
	/// </summary>
	public enum MaskType
	{
		/// <summary>
		/// Any characters are allowed
		/// </summary>
		Any,
		/// <summary>
		/// Only integers are allowed (digits and negative sign)
		/// </summary>
		Integer,
		/// <summary>
		/// Only decimals are allowed (digits, negative sign and decimal point)
		/// </summary>
		Decimal
	}
	#endregion
}